package org.fglxy.mockserver.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.DescriptorValidationException;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;

public class ProtoUtils {
	private static ConcurrentHashMap<String, ProtoDescriptor> DESCRIPTOR_MAP = new ConcurrentHashMap<String, ProtoUtils.ProtoDescriptor>();

	/*
	 * 创建描述结构。描述结构每次都重新创建，不使用旧的结果
	 */
	public static ProtoDescriptor createDescriptor(String mockId) {
		ProtoDescriptor descriptor = new ProtoDescriptor(mockId);
		try {
			descriptor.init();
			DESCRIPTOR_MAP.put(mockId, descriptor);
		} catch (DescriptorValidationException | IOException | URISyntaxException e) {
			throw new RuntimeException(e);
		}

		return descriptor;
	}

	/**
	 * 获取指定消息构建器
	 * 
	 * @param mockId
	 * @param cmdNo
	 * @return
	 */
	public static Builder getBuilderByCmdNo(String mockId, int cmdNo) {
		// 这个写法有并发安全风险，但是并发的可能性太低了，且这里没有删除操作。
		if (DESCRIPTOR_MAP.containsKey(mockId)) {
			return DESCRIPTOR_MAP.get(mockId).getBuilder(cmdNo);
		}
		return null;
	}

	public static class ProtoDescriptor {
		static Logger LOGGER = LoggerFactory.getLogger(ProtoDescriptor.class);
		private ConcurrentHashMap<String, Message.Builder> builderMap = new ConcurrentHashMap<>();
		private ConcurrentHashMap<Integer, String> cmdNoMap = new ConcurrentHashMap<>();

		private boolean initFlag;

		private String mockId;

		public ProtoDescriptor(String id) {
			this.mockId = id;
		}

		public Message.Builder getBuilder(String name) {
			if (this.builderMap.containsKey(name)) {
				return this.builderMap.get(name);
			}
			return null;
		}

		public Message.Builder getBuilder(int cmdNo) {
			if (this.cmdNoMap.containsKey(cmdNo)) {
				return getBuilder(this.cmdNoMap.get(cmdNo));
			}
			return null;
		}

		public void init() throws DescriptorValidationException, IOException, URISyntaxException {
			if (this.initFlag)
				return;
			this.initBuilder();
			this.initCmdNo();
			this.initFlag = true;
		}

		/**
		 * 解析cmdno协议映射关系
		 * @throws IOException 
		 */
		private void initCmdNo() throws IOException {
			cmdNoMap.clear();
			List<String> lines = ResourceUtils.getCmdNoMapByMockId(this.mockId);
			for (String line : lines) {
				if (line.trim().equals(""))
					continue;
				String[] params = line.split("=");
				if (params.length != 2)
					continue;
				this.cmdNoMap.put(Integer.parseInt(params[0].trim()), params[1].trim());
			}
		}

		public void initBuilder() throws DescriptorValidationException, IOException, URISyntaxException {
			this.builderMap.clear();
			try (InputStream fin = ResourceUtils.getDescriptorStreamByMockId(this.mockId)) {
				FileDescriptorSet descriptorSet = FileDescriptorSet.parseFrom(fin);
				LinkedList<FileDescriptorProto> protos = new LinkedList<>();
				Map<String, FileDescriptor> results = new HashMap<>();
				protos.addAll(descriptorSet.getFileList());
				while (!protos.isEmpty()) {
					FileDescriptorProto proto = protos.poll();
					FileDescriptor[] dependencies = checkDependencies(proto, results);
					if (dependencies != null) {
						parseFile(proto, dependencies, results);
					}
					else {
						protos.add(proto);
					}
				}
			}
		}

		/**
		 * 解析文件
		 * 
		 * @param proto
		 * @param dependencies
		 * @param results
		 * @throws DescriptorValidationException
		 */
		private void parseFile(FileDescriptorProto proto, FileDescriptor[] dependencies,
				Map<String, FileDescriptor> results) throws DescriptorValidationException {
			// 构造文件解释器
			FileDescriptor fileDescriptor = FileDescriptor.buildFrom(proto, dependencies);
			results.put(proto.getName(), fileDescriptor);
			// 获取文件中所有协议的解释器
			for (Descriptor descriptor : fileDescriptor.getMessageTypes()) {
				DynamicMessage.Builder builder = DynamicMessage.newBuilder(descriptor);
				this.builderMap.put(descriptor.getFullName(), builder);
			}
		}

		/**
		 * 检查依赖是否都已经初始化
		 * 
		 * @param proto   待检查的协议文件
		 * @param results 当前已经初始化好的协议文件
		 * @return 返回依赖列表，如果依赖没有完全初始化好则返回空
		 */
		private FileDescriptor[] checkDependencies(FileDescriptorProto proto, Map<String, FileDescriptor> results) {
			boolean canParse = true;
			FileDescriptor[] dependencies = new FileDescriptor[proto.getDependencyCount()];
			for (int i = 0; i < proto.getDependencyCount(); i++) {
				if (!results.containsKey(proto.getDependency(i))) {
					canParse = false;
					break;
				}
				dependencies[i] = results.get(proto.getDependency(i));
			}
			if (canParse) {
				return dependencies;
			} else {
				return null;
			}
		}
	}

}
